#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2011
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY strikesND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Author: Jesus Carrero <j.o.carrero@gmail.com>
# Mountain View, CA

""" common place holder for data needed for volatility calibration. """

from scipy import ones, asarray
from scipy.optimize import fmin, fmin_bfgs, fmin_ncg
import logging

# http://docs.scipy.org/doc/scipy/reference/tutorial/optimize.html


class CalibBase(object):
    """ virtual class to derive from. """

    __solts__ = ['m_strikes', 'm_prices', 'm_int_rate', 'm_t2maturity',
                 'm_style', 'm_contract', 'm_xtol', 'm_der', 'm_impvol',
                 'm_cali_method', 'm_log_progress']

    def __init__(self):
        """ instantiate object to type CalibBase."""

        if self.__class__ is CalibBase:
            raise NotImplementedError, "can't instantiate abstract base class"

        (self.m_strikes, self.m_prices, self.m_t2maturity) = (None, None, None)
        (self.m_der, self.m_hess, self.m_impvol) = (None, None, None)
        (self.m_style, self.m_int_rate, self.m_contract) = (None, None, None)

        self.m_xtol = 1e-8
        self.m_cali_method = None
        self.m_log_progress = logging.getLogger('CalibBase')

    def set_cal_data(self, strikes, prices, interest_rate,
            time2_mat, style, contract):
        """ set data for calibration.
            1.- strike prices.
            2.- contract present value.
            3.- interest rates.
            4.- time to maturity.
            5.- American, European, Bermudam.
            6.- Put, Call
        """

        self.m_strikes = asarray(strikes, 'float')
        self.m_prices = asarray(prices, 'float')
        self.m_int_rate = interest_rate * ones(self.m_strikes.shape)
        self.m_t2maturity = asarray(time2_mat, 'float')
        self.m_style = style
        self.m_contract = contract
        if not (self.m_strikes.shape == self.m_prices.shape
                and self.m_strikes.shape == self.m_t2maturity.shape):
            self.m_log_progress.error('missmatch in array size '
                    + str(strikes.size) + ' ' + str(prices.size) + ' '
                    + str(time2_mat.size))
            assert False

    def get_cal_data(self):
        """
            Strike, Price, Time, Style, Contract
        """
        return zip(self.m_strikes, self.m_prices, self.m_int_rate,
                self.m_t2maturity, self.m_style, self.m_contract)

    def get_times2_maturity(self):
        """ get time to maturity. """

        return self.m_t2maturity

    def set_initial_guess(self, initial_guess):
        """ set initial guess. """

        self.m_impvol = initial_guess

    def _set_gradient(self, der):
        """ set cost function analytical gradient."""

        self.m_der = der

    def _set_hessian(self, hess):
        """ set cost function hessian."""

        self.m_hess = hess

    def set_cal_method(self, cal_method):
        """ set calibration method. """

        if cal_method not in ['fmin', 'fmin_bfgs', 'fmin_ncg']:
            raise Warning('optmization scheme not sopported')
        self.m_cali_method = cal_method

    @classmethod
    def _cost_function(cls, value):
        """ virtual method to values cost function.
            could be defined outside this class because dose not make
            use of any member, but I prefer to keep it here.
        """

        raise NotImplementedError

    def _calibrate(self):
        """ calibrate model. """

        assert self.m_cali_method is not None

        xopt = None
        if 'fmin' == self.m_cali_method:
            xopt = fmin(self._cost_function, self.m_impvol,
                        xtol=self.m_xtol)
        elif 'fmin_bfgs' == self.m_cali_method:
            xopt = fmin_bfgs(self._cost_function, self.m_impvol,
                             fprime=self.m_der)
        elif 'fmin_ncg' == self.m_cali_method:
            xopt = fmin_ncg(self._cost_function, self.m_impvol,
                            fprime=self.m_der, fhess=self.m_hess)

        return xopt


